/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file stBasicTerrain.I
 * @author drose
 * @date 2010-10-12
 */

/**
 * Specifies the image filename that will define the height map of the
 * terrain.  This will require a subsequent call to load_data() to actually
 * read the data.
 */
INLINE void STBasicTerrain::
set_height_map(const Filename &height_map) {
  _height_map = height_map;
  _is_valid = false;
}

/**
 * Returns the image filename that defines the height map of the terrain.
 */
INLINE const Filename &STBasicTerrain::
get_height_map() const {
  return _height_map;
}

/**
 * Returns the length, in scene graph units, of one edge of the heightmap as
 * it is manifested by the terrain.  Increasing this number spreads the
 * heightmap out over a greater area.
 */
INLINE PN_stdfloat STBasicTerrain::
get_size() const {
  return _size;
}

/**
 * Convenience function to calculate the linear interpolation from A to B.
 */
INLINE PN_stdfloat STBasicTerrain::
interpolate(PN_stdfloat a, PN_stdfloat b, PN_stdfloat t) {
  return (a + (b - a) * t);
}

/**
 *
 */
template<class ValueType>
STBasicTerrain::InterpolationData<ValueType>::
InterpolationData() : _width(0), _height(0)
{
}

/**
 * Resets the array to an empty array of width x height cells.
 */
template<class ValueType>
void STBasicTerrain::InterpolationData<ValueType>::
reset(int width, int height) {
  _width = width;
  _height = height;
  _data.clear();
  _data.insert(_data.begin(), width * height, ValueType());
}

/**
 * Returns the value nearest to (u, v) in the data.
 */
template<class ValueType>
ValueType STBasicTerrain::InterpolationData<ValueType>::
get_nearest_neighbor(PN_stdfloat u, PN_stdfloat v) const {
  int u = int(u * _width + 0.5f);
  int v = int(v * _height + 0.5f);
  int index = u + v * _width;
  nassertr(index >= 0 && index < (int)_data.size(), 0);
  return _data[index];
}

/**
 * Interpolates the value at (u, v) between its four nearest neighbors.
 */
template<class ValueType>
ValueType STBasicTerrain::InterpolationData<ValueType>::
calc_bilinear_interpolation(PN_stdfloat u, PN_stdfloat v) const {
  u -= cfloor(u);
  v -= cfloor(v);

  u *= (PN_stdfloat)_width;
  v *= (PN_stdfloat)_height;

  const int lower_x = int(u);
  const int lower_y = int(v);
  const int higher_x = (lower_x + 1) % _width;
  const int higher_y = (lower_y + 1) % _height;

  const PN_stdfloat ratio_x = u - PN_stdfloat(lower_x);
  const PN_stdfloat ratio_y = v - PN_stdfloat(lower_y);
  const PN_stdfloat inv_ratio_x = 1.0f - ratio_x;
  const PN_stdfloat inv_ratio_y = 1.0f - ratio_y;

  nassertr(lower_x + lower_y * _width >= 0 && higher_x + higher_y * _width < (int)_data.size(), 0);

  const ValueType &t1 = _data[lower_x + lower_y * _width];
  const ValueType &t2 = _data[higher_x + lower_y * _width];
  const ValueType &t3 = _data[lower_x + higher_y * _width];
  const ValueType &t4 = _data[higher_x + higher_y * _width];

  return (t1 * inv_ratio_x + t2 * ratio_x) * inv_ratio_y +
    (t3 * inv_ratio_x + t4 * ratio_x) * ratio_y;
}

/**
 * Approximates the average value at (u, v) over the indicated radius,
 * assuming a polynomial curve.
 */
template<class ValueType>
ValueType STBasicTerrain::InterpolationData<ValueType>::
calc_smooth(PN_stdfloat u, PN_stdfloat v, PN_stdfloat radius) const {
  ValueType retval = 0;

  if (radius <= 0.0f) {
    retval = calc_bilinear_interpolation(u, v);

  } else {
    const PN_stdfloat test_points[9][2] = {
      {  0.0f * radius,   0.0f * radius },
      {  0.8f * radius,   0.0f * radius },
      { -0.8f * radius,   0.0f * radius },
      {  0.0f * radius,   0.8f * radius },
      {  0.0f * radius,  -0.8f * radius },
      {  0.25f * radius,  0.25f * radius },
      {  0.25f * radius, -0.25f * radius },
      { -0.25f * radius,  0.25f * radius },
      { -0.25f * radius, -0.25f * radius }
    };

    PN_stdfloat total_weight = 0.0f;
    for (int i = 0; i < 9; ++i) {
      const PN_stdfloat *test_point = test_points[i];
      PN_stdfloat weight = (1.0f - sqrt((test_point[0] * test_point[0]) + (test_point[1] * test_point[1])));
      total_weight += weight;
      retval += weight * calc_bilinear_interpolation(u + test_point[0], v + test_point[1]);
    }

    retval /= total_weight;
  }

  return retval;
}

/**
 * Returns true if the data is present--that is, reset() was called with non-
 * zero values--or false otherwise.
 */
template<class ValueType>
bool STBasicTerrain::InterpolationData<ValueType>::
is_present() const {
  return !_data.empty();
}
